package cn.tedu.charging.order.quartz;
import cn.tedu.charging.order.dao.repository.OrderRepository;
import cn.tedu.charging.order.pojo.po.ChargingBillFailPO;
import cn.tedu.charging.order.pojo.po.ChargingBillSuccessPO;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;




import java.util.Date;
import java.util.UUID;


/**
 * 设备自检 延迟处理定时任务
 * 目标 尽快发现设备故障
 * 如果没有自检任务：
 *      如果设备无响应，我们无法处理，成功订单表和失败订单表没有数据
 * 如果设备有响应，充了一会，设备故障，走死信队列，发现故障的时长会变长
 *
 * 解决措施：
 * 定时任务 ： 任务的执行时间 ： 当前时间 + 1分钟(设备自检的时间)
 *     如果设备无响应 1分钟就能发现，能快速的通知 用户 通知维修人员
 *     解决 设备开始就没有响应  开始就出故障
 *         死信队列 是检查 最后应该完成，而因为设备故障没有完成的订单 设备开始有响应，中间出故障
 */



@Slf4j
@Component
public class DeviceCheckJob implements Job, ApplicationContextAware {   // ApplicationContextAware 接口的主要目的是解决 Quartz 任务中无法自动注入 Spring Bean 的问题

//    private OrderRepository orderRepository;
    private static ApplicationContext applicationContext;   // 获取 Spring 容器引用

    private OrderRepository orderRepository;

    /**
     * Quartz 的机制：
     *      Quartz 通过反射调用无参构造方法创建 Job 实例
     *      不会经过 Spring 容器，因此 @Autowired 注入的依赖为 null
     * 解决方案：
     *      实现 ApplicationContextAware 获取 Spring 容器
     *      在 execute()方法中手动从容器获取需要的 Bean
     */

    // Quartz 需要的无参构造方法
    public DeviceCheckJob() {
    }

    // Spring IoC 容器注入 OrderRepository（用于被Spring管理的Bean）
    @Autowired
    public DeviceCheckJob(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    // 用于创建定时任务的构造方法
    public DeviceCheckJob(OrderRepository orderRepository, String orderNo, Integer gunId) throws SchedulerException {
        this.orderRepository = orderRepository;
        log.debug("添加设备自检定时任务:订单号:{},枪id:{}",orderNo,gunId);
        //创建调度器工厂
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
        //通过调度器工厂创建调度器
        Scheduler scheduler = schedulerFactory.getScheduler();

        JobDetail jobDetail = JobBuilder.newJob(DeviceCheckJob.class)
                //定义定时任务的名称和组
                .withIdentity("DeviceCheckJob" + UUID.randomUUID(),"device_check_jobs")
                //传入订单号
                .usingJobData("orderNo",orderNo)
                //传入枪id
                .usingJobData("gunId",gunId)
                .build();

        //定义任务触发的时间
        //当前时间 + 1分钟 (模拟设备自检需要1分钟)
        //当前时间
        long now = System.currentTimeMillis();
        // 1分钟
        long deviceCheckTime =  1 * 60 * 1000;
        //任务触发时间
        Date triggerTime = new Date(now + deviceCheckTime);

        //触发器
        Trigger trigger = TriggerBuilder.newTrigger().startAt(triggerTime).build();

        //调度器scheduler  把 任务 jobDetail 和  触发器 trigger 进行绑定
        scheduler.scheduleJob(jobDetail,trigger);
        //启动调用器
        scheduler.start();

    }

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        applicationContext = context;
    }





    /**
     * 执行 干一些事情
     * 7.判断是否有订单记录
     * 8.如果有成功订单记录，先不用管，无需处理，设备有响应的，设备给订单服务返回了开始充电成功指令
     * 9.如果没有成功订单记录，判断是否有失效订单记录
     * 10.如果有失败订单记录  设备有响应 设备故障-无法充电，设备给订单返回了开始充电失败指令，不用管
     * 11.如果没有失败订单记录 插入失败订单记录，故障原因：设备无响应
     */

    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        String orderNo = jobDataMap.getString("orderNo");
        Integer gunId = jobDataMap.getInt("gunId");



        // 通过 ApplicationContext 获取 OrderRepository Bean
        // 因为 Quartz 通过无参构造方法创建实例，orderRepository 为 null
        // 需要从 Spring 容器中获取
        if (orderRepository == null) {
            orderRepository = applicationContext.getBean(OrderRepository.class);
        }



        // 判断是否有订单记录
        // 通过Repository去数据库查询是否有成功的订单记录 条件是 orderNo
        // 获取成功订单记录
        ChargingBillSuccessPO chargingBillSuccessPO = orderRepository.getSuccessOrder(orderNo);
        // 判断是否有成功订单记录
        if (chargingBillSuccessPO == null){
            // 没有成功订单记录，获取失败订单记录
            ChargingBillFailPO oldChargingBillFailPO = orderRepository.getFailOrder(orderNo);
            // 判断是否有失败订单记录
            if (oldChargingBillFailPO == null){
                // 没有失败的订单记录，说明设备无响应，插入失败订单记录
                ChargingBillFailPO newChargingBillFailPO = new ChargingBillFailPO();
                newChargingBillFailPO.setFailDesc("设备自检失败-设备无响应");
                newChargingBillFailPO.setBillId(orderNo);
                log.debug("设备自检失败，保存失败订单记录:{}",newChargingBillFailPO);
                orderRepository.saveFailOrder(newChargingBillFailPO);
                log.debug("设备自检失败，保存失败订单记录:{},成功.",newChargingBillFailPO);

                // 通知设备维修人员
                log.debug("调用保障服务，通知维修人员对设备进行检查和维护,"+
                        "要告诉保障服务设备信息，或者订单号，然后通过订单号可以反查设备信息");

                /**
                 * 第一种方式：
                 * open-feign 和 保障服务通信
                 * 1.创建保障服务微服务
                 * 2.创建一个接口，创建维修单 入参
                 * 3.维修单对应的表
                 *      id 订单信息(订单号)
                 *      设备信息(站id，桩id，枪id)
                 *      维修人员信息(id)
                 *      维修单信息(维修单号，status(新创建，完成，修理中)，时间)
                 * 4.订单服务增加，保障服务 feignClient
                 * 5.open-feign 注入 feignClient 直接调用
                 */

                /**
                 * 第二种方式：
                 * rabbitMQ消息队列 和 保证服务通信
                 * 1.定义保障服务 Exchange 和 queue 并通过 RoutingKey 绑定
                 * 2.定义保障服务Producer 用来给 保障服务 Exchange 发消息
                 * 3.消息的格式可以是json的格式 {orderNO:"112233",设备信:{}}
                 * 4.创建保障服务微服务
                 * 5.创建保障服务微服务queue的消费者 @RabbitListener 监听 保障服务的queue
                 * 6.生成维修单
                 *
                 * 保障服务，都需要考虑 幂等性，保障同一个订单不能搞出多个维修单
                 *
                 * 生成维修单后，可以让维修师傅来抢单(其他业务的衍生)
                 */

                // 通知充电用户   todo 实时通知用户
                log.debug("通知充电用户，请您更换充电设备，给您带来的不便，请谅解...");
                // 给用户发生优惠券
                log.debug("调用 营销系统(优惠券服务),给用户发送优惠券 1张价值100元的优惠券已经发放到您的账户");
            }else {
                // 有失败订单记录  无需修改失败原因，真实的原因是  有响应-无法充电  不能改成 无响应
                log.debug("设备自检失败-设备有响应，但是充不了电，请您更换充电设备，这里逻辑无需修改设备的失败原因");
            }
        }else {
            // 有成功订单记录
            log.debug("有成功订单记录，表示设备自检成功，有响应，能充电");
            log.debug("更新设备信息到订单成功表，枪id:{}，订单编号:{}，这里不是必须的操作，为了演示，给成功订单表填充设备信息");
            orderRepository.updateDeviceInfo(orderNo,gunId);
            log.debug("更新设备信息到订单成功表成功,枪id:{},订单编号:{},这里不是必须的操作,为了演示,给成功订单表填充设备信息");
        }


    }




}
